package com.panpass.idempotent.aspect;

import com.panpass.idempotent.annotation.Idempotent;
import com.panpass.idempotent.config.IdempotentProperties;
import com.panpass.idempotent.exception.IdempotentException;
import com.panpass.idempotent.utils.RedisHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.util.Arrays;

/**
 * 幂等切面
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class IdempotentAspect {
    private final RedisHelper redisHelper;
    private final IdempotentProperties properties;
    private final ExpressionParser parser = new SpelExpressionParser();

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
        // 1. 获取幂等key
        String key = StringUtils.hasText(idempotent.key()) ?
                generateKeyBySpEl(point, idempotent.key()) :
                generateDefaultKey(point);

        // 2. 尝试设置key
        boolean success = redisHelper.trySet(
                key,
                idempotent.expire(),
                idempotent.timeUnit()
        );

        // 3. 如果key已存在，说明是重复请求
        if (!success) {
            String message = StringUtils.hasText(idempotent.message()) ?
                    idempotent.message() : properties.getDefaultMessage();
            log.warn("Duplicate request, key:{}, method:{}",
                    key, point.getSignature().getName());
            throw new IdempotentException(message);
        }

        try {
            // 4. 执行目标方法
            return point.proceed();
        } finally {
            // 5. 根据配置决定是否删除key(不删除，到期后也会自动过期)
            if (idempotent.deleteKeyAfterExecution()) {
                log.info("Deleting key: {}", key);
                redisHelper.delete(key);
            } else {
                log.info("Key retained: {}", key);
            }
        }
    }

    /**
     * 使用SpEL表达式生成key
     */
    private String generateKeyBySpEl(ProceedingJoinPoint point, String spEl) {
        try {
            EvaluationContext context = new StandardEvaluationContext();

            // 获取方法参数
            MethodSignature signature = (MethodSignature) point.getSignature();
            String[] paramNames = signature.getParameterNames();
            Object[] args = point.getArgs();

            // 设置SpEL变量
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }

            // 解析表达式
            Expression expression = parser.parseExpression(spEl);
            Object value = expression.getValue(context);
            return properties.getPrefix() + (value != null ? value.toString() : "");
        } catch (Exception e) {
            log.error("Generate key by SpEL error", e);
            throw new IdempotentException("生成幂等键失败");
        }
    }

    /**
     * 生成默认key
     * 格式: prefix + class:method:argsHash
     */
    private String generateDefaultKey(ProceedingJoinPoint point) {
        try {
            // 获取类名
            String className = point.getTarget().getClass().getSimpleName();

            // 获取方法名
            String methodName = point.getSignature().getName();

            // 获取参数hash
            String argsHash = Arrays.deepHashCode(point.getArgs()) + "";

            // 组装key
            return properties.getPrefix() + className + ":" + methodName + ":" + argsHash;
        } catch (Exception e) {
            log.error("Generate default key error", e);
            throw new IdempotentException("生成幂等键失败");
        }
    }
}